package bots;

import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.Vector;

import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction;

import belette.Balle;
import belette.IBelette;

import applets.BattleField;

import surface.PolylineObject;
import surface.Surface;
import utils.AStar;
import utils.AStarParam;
import utils.Noeud;
import utils.Vector2d;

public class Soldier implements IBot {

	private final static float epsilon = (float) 7;
	Vector2d coords;
	float radius;
	private int color;
	private static Surface surface ;

	ArrayList<IBot> seenEnemies;

	//variables pour le pathfinding
	Vector2d destination;
	Vector2d closestDestination;
	Vector2d vitesse;
	Vector2d acceleration;
	Noeud firstNode;
	Noeud currentNode;
	Noeud lastNode;
	HashMap<Noeud, Noeud> fathers;
	boolean lastNodeReach;
	private static ArrayList<Noeud> waypoints;
	private static HashMap<Noeud, Integer> waypointsToIndex;
	private static Integer[][] waypointsToWaypoints;
	private static Double[][] waypointsToWaypointsDistance;


	//variables utiles à la gestion des etats
	int currentState;
	IBot enemyEngaging;
	public static final int LookingForEnemies = 1;
	public static final int EngagingEnemy = 2;
	public static final int Firing = 3;
	public static final int Fleeing = 4;

	public static final float portee = 75F;

	//variables utilies au steering behavior
	float maxvelocity;
	float maxAcceleration;
	float velocity;
	float distanceToStop;
	float distanceToDecelerate;
	private float life;
	private Vector2d drawaim;
	private Vector2d aim;
	private static boolean alreadyInited = false;


	public Soldier(Vector2d co, float rad, Surface s, int color){
		this.color = color;
		coords=co;
		radius=rad;
		destination = null;
		closestDestination = null;
		maxvelocity=5F;
		distanceToStop = 1 * maxvelocity;
		distanceToDecelerate = 15 * maxvelocity;
		maxAcceleration = maxvelocity * 0.1f;
		velocity=0F;
		surface=s;
		acceleration = new Vector2d(0,0);
		vitesse = new Vector2d(0,0);
		firstNode = null;
		currentNode = null;
		lastNode = null;
		fathers = null;
		lastNodeReach = false;
		currentState = LookingForEnemies;
		seenEnemies=new ArrayList<IBot>();
		life=100;

		aim=new Vector2d(1,0);
		if(!alreadyInited){
			createWaypoints();
			alreadyInited = true;
		}
	}

	@Override
	public Vector2d getCoord() {

		return coords;
	}

	@Override
	public float botRadius() {

		return radius;
	}

	@Override
	public void draw(Graphics g) {
		if(destination != null){
			g.setColor(Color.GREEN);
			g.fillOval((int)destination.x - 3, (int)destination.y - 3, 6, 6);
		}

		switch (color){
		case BattleField.RED :
			g.setColor(Color.RED);
			break;
		case BattleField.BLUE :
			g.setColor(Color.BLUE);
			break;
		}
		g.fillOval((int) (coords.x-radius), (int)(coords.y-radius), (int)radius*2, (int)radius*2);
		g.drawOval((int) (coords.x-radius - portee), (int)(coords.y-radius - portee), (int)(radius+portee)*2, (int)(radius+portee)*2);


		if(waypoints!=null)
			for(Noeud n : waypoints){
				g.fillOval((int)n.getPosition().x-3,(int) n.getPosition().y-3, 3*2, 3*2);
			}

		for(Noeud n : waypoints){
			for(Noeud neighbour : n.getMyNeighbours()){
				g.drawLine((int)n.getPosition().x, (int)n.getPosition().y, (int)neighbour.getPosition().x,(int)neighbour.getPosition().y);
			}

		}
		g.setColor(Color.CYAN);
		g.drawLine((int)(coords.x),(int)( coords.y), (int)(drawaim.x),(int)( drawaim.y));
	}

	@Override
	public void AI() {

		updateBulletColision();

		currentState = resolveState(currentState);
		switch(currentState){
		case LookingForEnemies :
			if(destination == null){
				choosePatrolDestination();
			}
			goTo();
			break;
		case Fleeing:
			if(destination == null){
				choosePatrolDestination();
			}
			goTo();
			break;
		case Firing:
			//System.out.println("Piou piou");
			destination=enemyEngaging.getCoord();
			aim=enemyEngaging.getCoord().subtract(getCoord());
			aim.setNormalize();
			if(new Random().nextFloat()<0.1f) new Balle(this,portee);
			int i =1;
			if(new Random(System.currentTimeMillis()).nextFloat()<0.05)i=-i; 
			destination = coords.add(new Vector2d(i*aim.y,(-i)*aim.x).scale(10));

			goTo();
			break;
		case EngagingEnemy:
			if(enemyEngaging==null) break;
			//System.out.println("Engage enemy");
			destination=enemyEngaging.getCoord();
			goTo();
			break;
		}

		drawaim=coords.add(aim.scale(radius));


	}




	private void updateBulletColision() {
		for(IBelette b : BattleField.bullets){
			if(b.isActive() && !b.firedBy().equals(this) && b.getCoords().distance(getCoord())<b.getRadius()+radius){


				life-=b.getPower();
				b.hitBot(this);
				System.out.println("HIT "+color+" "+life);
			}
		}

	}

	private void choosePatrolDestination() {
		Random r = new Random();
		Vector2d nextPosition = new Vector2d(r.nextInt(surface.wxsize) , r.nextInt(surface.wysize));
		while(surface.isInside(new Vector2d(-1, -1), nextPosition)){
			nextPosition.x = r.nextInt(surface.wxsize);
			nextPosition.y = r.nextInt(surface.wysize);
		}
		destination = nextPosition;
	}



	public IBot chooseTarget(ArrayList<IBot> enemies){
		double distanceMin = Double.MAX_VALUE;
		IBot target = null;
		for(IBot e : enemies){
			double dist = coords.distance(e.getCoord());
			if( dist < distanceMin){
				target = e;
				distanceMin = dist;
			}
		}
		return target;
	}

	public ArrayList<IBot> sawEnemy(){
		return seenEnemies;
	}


	public boolean saw(IBot bot){
		return surface.cansee(coords, bot.getCoord());
	}

	public void updateCloseEnemies(){
		if(seenEnemies!=null) seenEnemies.clear();
		if (getColor()==BattleField.BLUE){
			for(IBot i : BattleField.redTeam){
				if (saw(i)){
					seenEnemies.add(i);
					//System.out.println("Red Enemy seen");
				}
			}
		} else {
			if (getColor()==BattleField.RED)
				for(IBot i : BattleField.blueTeam){
					if (saw(i)){
						seenEnemies.add(i);
						//System.out.println("Blue Enemy seen");
					}
				}
		}
	}

	public int resolveState(int state){


		ArrayList<IBot> enemies;
		updateCloseEnemies();
		switch (state){
		case LookingForEnemies :
			enemies = sawEnemy();
			if(enemies != null){
				IBot enemy = chooseTarget(enemies);
				enemyEngaging = enemy; 
				if(enemyEngaging !=null &&  enemyEngaging.getCoord().distance(getCoord()) <= portee){
					return Firing;
				}
				else{
					return EngagingEnemy;
				}
			}
			return LookingForEnemies;
		case Firing :
			if(life<20) return Fleeing;
			
			if(enemyEngaging == null){//si l'enemie que l'on suivait est mort on cherche de nouveaux enemies
				
				enemies = sawEnemy();
				if(enemies != null){
					IBot enemy = chooseTarget(enemies);
					enemyEngaging = enemy;
					if(enemy.getCoord().distance(getCoord()) <= portee){
						return Firing;
					}
					else{
						return EngagingEnemy;
					}
				}
			}
			else if(!surface.cansee(coords, enemyEngaging.getCoord())) return LookingForEnemies;
				else
				if(enemyEngaging.getCoord().distance(getCoord()) > portee){
				enemies = sawEnemy();
				if(enemies != null){
					IBot enemy = chooseTarget(enemies);
					if(enemy !=null && enemy.getCoord().distance(getCoord()) <= portee){
						enemyEngaging = enemy;
						return Firing;
					}
				}
				return EngagingEnemy;
			}
			return Firing;

		case EngagingEnemy :
			if(enemyEngaging==null) return LookingForEnemies;
			if(!surface.cansee(coords, enemyEngaging.getCoord())) return LookingForEnemies;
			if(!saw(enemyEngaging)){
				enemies = sawEnemy();
				if(enemies != null){
					IBot enemy = chooseTarget(enemies);
					if(enemy!=null && enemy.getCoord().distance(getCoord()) <= portee){
						enemyEngaging = enemy;
						return Firing;
					}
				}
			}
			if(enemyEngaging.getCoord().distance(coords) <= portee+10) return Firing;
			return EngagingEnemy;
		case Fleeing :
			return Fleeing;

		default :
			return LookingForEnemies;
		}
	}



	public void goTo(){
		if(destination != null){
			//on voit directement le point de destination on y va en ligne droite
			Vector2d whereToGo = coords;

			if(currentNode == null){
				currentNode = getClosestWaypoint(coords);
			}
			if(lastNode == null){
				lastNode = getClosestWaypoint(destination);
			}
			if(fathers == null){
				SoldierParamPathFinding param = new SoldierParamPathFinding(lastNode);
				AStar astar = new AStar();
				fathers = astar.performe(currentNode, param);
			}

			if(surface.cansee(coords, destination)){
				whereToGo = destination;
			} else
				if(coords.distance(lastNode.getPosition()) <25 ){
					lastNodeReach = true;
				}
				else if(coords.distance(currentNode.getPosition()) <25){
					Noeud previous = null;
					Noeud nodeToGo = lastNode;
					while(nodeToGo != currentNode){
						previous = nodeToGo;
						nodeToGo = fathers.get(nodeToGo);
					}
					if(previous == null){
						currentNode = lastNode;
						whereToGo = destination;
					}else{
						currentNode = previous;
						whereToGo = currentNode.getPosition();
					}
				}
				else{
					if(lastNodeReach){
						whereToGo = destination;
					}
					else{
						whereToGo = currentNode.getPosition();
					}
				}
			if(lastNodeReach){
				whereToGo = destination;
			}
			directGoToPoint(whereToGo, false);
			//goToPoint(whereToGo, false);
			if(coords.distance(destination)<5){
				destination = null;
				fathers = null;
				currentNode = null;
				lastNode = null;
				lastNodeReach = false;
			}
		}	
	}



	//version du goTo ou le bot vas directement vers le point le plus proche de la destination 
	//qu'il voit y compris le point destination lui même
	//	public void goTo(){
	//		if(destination != null){
	//			//on voit directement le point de destination on y va en ligne droite
	//			Vector2d whereToGo = coords;
	//			boolean last = true;
	//			if(surface.cansee(coords, destination)){
	//				whereToGo = destination;
	//			}
	//			else{
	//				if(currentNode == null){
	//					currentNode = getClosestWaypoint(coords);
	//				}
	//				if(lastNode == null){
	//					lastNode = getClosestWaypoint(destination);
	//				}
	//				if(fathers == null){
	//					SoldierParamPathFinding param = new SoldierParamPathFinding(lastNode);
	//					AStar astar = new AStar();
	//					fathers = astar.performe(currentNode, param);
	//				}
	//				if(coords.distance(lastNode.getPosition()) <= 8 ){
	//					fathers = null;
	//					currentNode = null;
	//					lastNode = null;
	//				}
	//				else{
	//					Noeud nodeToGo = lastNode;
	//					if (fathers != null){
	//						while(!surface.cansee(nodeToGo.getPosition(), coords)){
	//							nodeToGo = fathers.get(nodeToGo);
	//						}
	//						currentNode = nodeToGo;
	//					}
	//					whereToGo = currentNode.getPosition();
	//				}
	//			}
	//			directGoToPoint(whereToGo, last);
	//			if(coords.equals(destination)){
	//				destination = null;
	//			}
	//		}	
	//	}


	//	@Override
	//	public void AI() {
	//		if(destination != null){
	//			//on voit directement le point de destination on y va en ligne droite
	//			Vector2d whereToGo = coords;
	//			boolean last = true;
	//			if(surface.cansee(coords, destination)){
	//				whereToGo = destination;
	//			}
	//			else{
	//				if(currentNode == null){
	//					currentNode = getClosestWaypoint(coords);
	//				}
	//				if(lastNode == null){
	//					lastNode = getClosestWaypoint(destination);
	//				}
	//				int indexCurrentNode = waypointsToIndex.get(currentNode);
	//				int indexLastNode = waypointsToIndex.get(lastNode);
	//				int indexNodeToGo = indexLastNode;
	//				while(indexNodeToGo != -1 && !surface.cansee(coords, waypoints.get(indexNodeToGo).getPosition())){
	//					indexNodeToGo =  waypointsToWaypoints[indexCurrentNode][indexNodeToGo];
	//				}
	//				if(indexNodeToGo != -1){
	//					currentNode = waypoints.get(indexNodeToGo);
	//					whereToGo = currentNode.getPosition();
	//				}
	//			}
	//			goToPoint(whereToGo, last);
	//		}			
	//	}


	private void directGoToPoint(Vector2d dest, boolean stopAfter){
		Vector2d v = new Vector2d(dest.x,dest.y);
		if( dest.distance(coords) <= 5){
			vitesse = Vector2d.zero;
			coords = v;
		}
		else{
			vitesse = v.subtract(coords);
			vitesse.setNormalize();
			vitesse = vitesse.scale(maxvelocity);
		}


	}


	//	private void goToPoint(Vector2d dest, boolean stopAfter){
	//		
	//		if(dest.distance(coords)<50){
	//			directGoToPoint(dest, stopAfter);
	//			return;
	//		}
	//		Vector2d futureCoords = new Vector2d();
	//		futureCoords.setScale(4F, vitesse);
	//		if(!surface.cansee(coords, coords.add(vitesse))){
	//			vitesse = Vector2d.zero;
	//			return;
	//		}
	//		
	//		if(distanceToStop > dest.distance(coords)){
	//			
	////			vitesse.setDiff(dest, coords);
	//			float distance = dest.distance(coords);
	//			float rampedSpeed = maxvelocity * (distance / 200);
	//			float clippedSpeed = Math.min(rampedSpeed, maxvelocity);
	//			acceleration.setScale(clippedSpeed / distance, acceleration);
	//			vitesse.setDiff(acceleration, vitesse);
	//		}
	//		else {
	//			acceleration.setDiff(dest, coords);
	//			acceleration.setNormalize();
	//			acceleration.setScale(maxAcceleration, acceleration);
	//			vitesse.setSum(vitesse, acceleration);
	//			
	//			if (vitesse.magnitude() > maxvelocity) {
	//				vitesse.setNormalize();
	//				vitesse.setScale(maxvelocity, vitesse);
	//			}
	//		}
	//		
	//		
	//		
	//	}




	private static void createWaypoints() {
		System.out.println("creating waypoints");
		waypoints = new ArrayList<Noeud>();
		waypointsToIndex = new HashMap<Noeud, Integer>();

		int pas = 30;
		//on quadrille de waypoints
		for(int i=0;i<surface.wxsize;i+=pas){
			for(int j=0;j<surface.wysize;j+=pas){
				Vector2d a = new Vector2d();
				a.set(-5f,-5f);

				Vector2d b = new Vector2d(i+0.01f, j+0.01f);
				if(!surface.isInside(a,b)){
					Noeud toAdd = new  Noeud(b);
					waypoints.add(toAdd);
				}
			}
		}

		//on encadre les polygones de waypoints
		for(PolylineObject o : surface.getObjects()){
			Vector<Vector2d> homotethie = new Vector<Vector2d>();
			Vector2d A;
			Vector2d B;
			Vector2d C;

			//ajout des waypoints représentant les sommets de la figure agrandie.
			for(int i = 0; i<o.nbPoints ; i++){
				if(i == 0 ){
					A = o.globalCoordPoints.get(o.nbPoints-1);
					B = o.globalCoordPoints.get(i);
					C = o.globalCoordPoints.get(i+1);
				}
				else if( i == o.nbPoints - 1){
					A = o.globalCoordPoints.get(i-1);
					B = o.globalCoordPoints.get(i);
					C = o.globalCoordPoints.get(0);
				}
				else{
					A = o.globalCoordPoints.get(i-1);
					B = o.globalCoordPoints.get(i);
					C = o.globalCoordPoints.get(i+1);
				}
				Vector2d comp1 = new Vector2d(B.x - A.x , B.y - A.y);
				comp1.setNormalize();
				Vector2d comp2 =  new Vector2d(B.x - C.x , B.y - C.y);
				comp2.setNormalize();

				Vector2d bisectrice = new Vector2d(comp1.x + comp2.x , comp1.y + comp2.y);
				bisectrice.setNormalize();
				bisectrice.setScale((float) (epsilon), bisectrice);
				Vector2d result = new Vector2d(B.x+bisectrice.x, B.y+bisectrice.y);
				homotethie.add(i , result);
				Noeud toAdd = new  Noeud(result);
				waypoints.add(toAdd);
			}
			//ajout de waypoints le long des arrètes 
			for(int i = 0 ; i < homotethie.size() ; i++){
				A = homotethie.get(i);
				if( i != homotethie.size() - 1 ){
					B = homotethie.get(i + 1);
				}
				else{
					B = homotethie.get(0);
				}
				Vector2d vectToAdd = new Vector2d(B.x - A.x, B.y - A.y);
				vectToAdd.setNormalize();
				float distance = (float) Math.sqrt( Math.pow((B.x - A.x), 2) + Math.pow((B.y - A.y), 2));
				int k = 1;
				double ecartement = distance / (double) ((int) (distance/pas));
				vectToAdd.setScale((float)ecartement, vectToAdd);
				while(distance >= (k) * ecartement){
					Vector2d result = new Vector2d(A.x+ k * vectToAdd.x, A.y + k * vectToAdd.y);
					Noeud toAdd = new  Noeud(result);
					waypoints.add(toAdd);
					k++;
				}
			}
		}

		//on supprime les waypoints qui se retrouve dans un polygone
		Vector2d out1 = new Vector2d(-10, -10);
		Vector2d out2 = new Vector2d(-10, -3);
		for(int i = 0 ; i < waypoints.size() ; i++){
			Noeud n =  waypoints.get(i);
			if(surface.isInside(out1, n.getPosition()) || surface.isInside(out2, n.getPosition())){
				waypoints.remove(i);
			}
		}
		//on ajoute les liaisons entre waypoints et on remplie la hashmapWaypointsToIndex
		int indexwti = 0;
		for(Noeud n1  : waypoints){
			waypointsToIndex.put(n1, indexwti);
			indexwti++;
			for(Noeud n2  : waypoints){
				if(n1.getDistance(n2)<pas*1.5){
					if(surface.cansee(n1.getPosition() ,n2.getPosition() ))
						n1.getMyNeighbours().add(n2);
				}
			}
		}



		//		//on applique dijkstra pour remplir le tableau des
		//		int nbWaypoints = waypoints.size();
		//		waypointsToWaypoints = new Integer[nbWaypoints][nbWaypoints];
		//		waypointsToWaypointsDistance = 	new Double[nbWaypoints][nbWaypoints];	
		//		
		//		for(int i = 0 ; i < nbWaypoints ; i++ ){
		//			Double[] distanceIToJ = new Double[nbWaypoints];
		//			Integer[] tabPere = new Integer[nbWaypoints];
		//			//indexToVisit.get(1)
		//			HashSet<Integer> indexToVisit = new HashSet<Integer>();
		//			for(int j = 0 ; j < nbWaypoints ; j++){
		//				distanceIToJ[j] = -1.0;
		//				tabPere[j] = i;
		//			}
		//			distanceIToJ[i] = 0.0;
		//			tabPere[i] = -1;
		//			indexToVisit.add(i);
		//			int toVisit = i;
		//			while(!indexToVisit.isEmpty()){
		//				indexToVisit.remove(toVisit);
		//				Noeud visiting = waypoints.get(toVisit);
		//				int currentIndex;
		//				double currentDistance;
		//				for(Noeud n : visiting.getMyNeighbours() ){
		//					currentIndex = waypointsToIndex.get(n);
		//					currentDistance = visiting.getDistance(n);
		//					if(distanceIToJ[currentIndex] == - 1 || 
		//							distanceIToJ[currentIndex] > currentDistance + distanceIToJ[toVisit]){
		//						
		//						distanceIToJ[currentIndex] = currentDistance + distanceIToJ[toVisit];
		//						tabPere[currentIndex] = toVisit;
		//						indexToVisit.add(currentIndex);
		//					}
		//				}
		//				double min = -1;
		//				for(Integer index : indexToVisit){
		//					if(distanceIToJ[index] < min || min == -1){
		//						min =distanceIToJ[index];
		//						toVisit = index;
		//					}
		//				}
		//			}
		//			waypointsToWaypoints[i] = tabPere;
		//			waypointsToWaypointsDistance[i] = distanceIToJ;
		//		}
		//		double timeE = System.nanoTime();
		//		System.out.println("waypoints Created in "+(timeE-timeB) / Math.pow(10.0, 9.0) + " secondes");
	}





	@Override
	public void updatePosition() {
		// go to direction
		if(vitesse!=null)
			coords.setSum(coords, vitesse);

	}

	/**
	 * @param destination the destination to set
	 */
	public void setDestination(Vector2d destination) {
		this.destination = destination;
	}

	public Noeud getClosestWaypoint(Vector2d position){
		double min = -1;
		Noeud result = null;
		for(Noeud n : waypoints){
			double dist = n.getPosition().distance(position);
			if(min == -1 || dist < min){
				result = n;
				min = dist;
			}
		}
		return result;
	}

	@Override
	public int getColor() {

		return this.color;
	}

	@Override
	public float getLife() {

		return life;
	}

	@Override
	public boolean hit(float power) {
		life-=power;
		if(life<0) {
			return true;
		}
		return false;
	}

	@Override
	public boolean isAlive() {
		return life>0;
	}

	@Override
	public Vector2d getAim() {

		return aim;
	}





}
